/*
 *  Blazon: A user datagram protocol re-braodcasting daemon.
 *  event.c: Event scheduling and handling functions
 *
 *  Copyright 2012 Blazon development team
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.

 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *  Code borrowed from Squid.
 *
 *  AUTHOR: Henrik Nordstrom
 *  SQUID Web Proxy Cache          http://www.squid-cache.org/
 *  ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 */

#include "std-include.h"

#include "blazon.h"
#include "tools.h"
#include "event.h"

#define EV_NAME_LEN 33
static char last_event_ran[EV_NAME_LEN];
static dlink_list event_list;

static time_t event_time_min = -1;

/*
 * struct ev_entry * 
 * event_find(EVH *func, void *arg)
 *
 * Input: Event function and the argument passed to it
 * Output: Index to the slow in the event_table
 * Side Effects: None
 */
static struct ev_entry *
event_find(EVH * func, void *arg)
{
	dlink_node *ptr;
	struct ev_entry *ev;
	DLINK_FOREACH(ptr, event_list.head)
	{
		ev = ptr->data;
		if((ev->func == func) && (ev->arg == arg))
			return ev;
	}

	return NULL;
}

/*
 * struct ev_entry * 
 * event_add(const char *name, EVH *func, void *arg, time_t when)
 *
 * Input: Name of event, function to call, arguments to pass, and frequency
 *	  of the event.
 * Output: None
 * Side Effects: Adds the event to the event list.
 */
struct ev_entry *
event_add(const char *name, EVH * func, void *arg, time_t when)
{
	struct ev_entry *ev;
	ev = my_malloc(sizeof(struct ev_entry));
	ev->func = func;
	ev->name = my_strndup(name, EV_NAME_LEN);
	ev->arg = arg;
	ev->when = CURRENT_TIME + when;
	ev->next = when;
	ev->frequency = when;

	if((ev->when < event_time_min) || (event_time_min == -1))
	{
		event_time_min = ev->when;
	}
	dlink_add(ev, &ev->node, &event_list);
	return ev;
}

struct ev_entry *
event_addonce(const char *name, EVH * func, void *arg, time_t when)
{
	struct ev_entry *ev;
	ev = my_malloc(sizeof(struct ev_entry));
	ev->func = func;
	ev->name = my_strndup(name, EV_NAME_LEN);
	ev->arg = arg;
	ev->when = CURRENT_TIME + when;
	ev->next = when;
	ev->frequency = 0;

	if((ev->when < event_time_min) || (event_time_min == -1))
		event_time_min = ev->when;

	dlink_add(ev, &ev->node, &event_list);
	return ev;
}

/*
 * void event_delete(struct ev_entry *ev)
 *
 * Input: pointer to ev_entry for the event
 * Output: None
 * Side Effects: Removes the event from the event list
 */
void
event_delete(struct ev_entry *ev)
{
	if(ev == NULL)
		return;

	dlink_delete(&ev->node, &event_list);
	my_free(ev->name);
	my_free(ev);
}

/*
 * void event_find_delete(EVH *func, void *arg)
 *
 * Input: pointer to func and data
 * Output: None
 * Side Effects: Removes the event from the event list
 */
void
event_find_delete(EVH * func, void *arg)
{
	event_delete(event_find(func, arg));
}

/* 
 * struct ev_entry *
 * event_addish(const char *name, EVH *func, void *arg, time_t delta_isa)
 *
 * Input: Name of event, function to call, arguments to pass, and frequency
 *	  of the event.
 * Output: None
 * Side Effects: Adds the event to the event list within +- 1/3 of the
 *	         specified frequency.
 */
struct ev_entry *
event_addish(const char *name, EVH * func, void *arg, time_t delta_ish)
{
	if(delta_ish >= 3.0)
	{
		const time_t two_third = (2 * delta_ish) / 3;
		delta_ish = two_third + ((rand() % 1000) * two_third) / 1000;
		/*
		 * XXX I hate the above magic, I don't even know if its right.
		 * Grr. -- adrian
		 */
	}
	return event_add(name, func, arg, delta_ish);
}


void
run_event(struct ev_entry *ev)
{
	strlcpy(last_event_ran, ev->name, sizeof(last_event_ran));
	ev->func(ev->arg);
	if(!ev->frequency)
	{
		dlink_delete(&ev->node, &event_list);
		my_free(ev);
		return;
	}
	ev->when = CURRENT_TIME + ev->frequency;
	if((ev->when < event_time_min) || (event_time_min == -1))
		event_time_min = ev->when;
}

/*
 * void event_run(void)
 *
 * Input: None
 * Output: None
 * Side Effects: Runs pending events in the event list
 */
void
event_run(void)
{
	dlink_node *ptr, *next;
	struct ev_entry *ev;

	event_time_min = -1;
	DLINK_FOREACH_SAFE(ptr, next, event_list.head)
	{
		ev = ptr->data;
		if(ev->when <= CURRENT_TIME)
		{
			strlcpy(last_event_ran, ev->name, sizeof(last_event_ran));
			ev->func(ev->arg);

			/* event is scheduled more than once */
			if(ev->frequency)
			{
				ev->when = CURRENT_TIME + ev->frequency;
				if((ev->when < event_time_min) || (event_time_min == -1))
					event_time_min = ev->when;
			}
			else
			{
				event_delete(ev);
				/*dlink_delete(&ev->node, &event_list);
				my_free(ev->name);
				my_free(ev);*/
			}
		}
		else
		{
			if((ev->when < event_time_min) || (event_time_min == -1))
				event_time_min = ev->when;
		}
	}
}


/*
 * void event_init(void)
 *
 * Input: None
 * Output: None
 * Side Effects: Initializes the event system. 
 */
void
event_init(void)
{
	strlcpy(last_event_ran, "NONE", sizeof(last_event_ran));
}

void
dump_events(void (*func) (char *, void *), void *ptr)
{
	int len;
	char buf[512];
	dlink_node *dptr;
	struct ev_entry *ev;
	len = sizeof(buf);

	snprintf(buf, len, "Last event to run: %s", last_event_ran);
	func(buf, ptr);

	strlcpy(buf, "Operation                    Next Execution", len);
	func(buf, ptr);

	DLINK_FOREACH(dptr, event_list.head)
	{
		ev = dptr->data;
		snprintf(buf, len, "%-28s %-4ld seconds", ev->name,
				ev->when - (long)CURRENT_TIME);
		func(buf, ptr);
	}
}


/*
 * void 
 * clear_all_events(void)
 *
 * Input:
 * Output: 
 * Side Effects: Clears all events from the event_list
 */
void
clear_all_events(void)
{
	dlink_node *ptr, *next;
	struct ev_entry *ev;

	DLINK_FOREACH_SAFE(ptr, next, event_list.head)
	{
		ev = ptr->data;

		dlink_delete(&ev->node, &event_list);
		my_free(ev->name);
		my_free(ev);
	}
}


/* 
 * void set_back_events(time_t by)
 * Input: Time to set back events by.
 * Output: None.
 * Side-effects: Sets back all events by "by" seconds.
 */
void
set_back_events(time_t by)
{
	dlink_node *ptr;
	struct ev_entry *ev;
	DLINK_FOREACH(ptr, event_list.head)
	{
		ev = ptr->data;
		if(ev->when > by)
			ev->when -= by;
		else
			ev->when = 0;
	}
}

void
event_update(struct ev_entry *ev, time_t freq)
{
	if(ev == NULL)
		return;

	ev->frequency = freq;

	/* update when its scheduled to run if its higher
	 * than the new frequency
	 */
	if((CURRENT_TIME + freq) < ev->when)
		ev->when = CURRENT_TIME + freq;
	return;
}

time_t
event_next(void)
{
	return event_time_min;
}
